/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.panpf.sketch.sample;

import me.panpf.sketch.SLog;
import me.panpf.sketch.util.ObjectPool;
import me.panpf.sketch.util.SketchUtils;
import me.panpf.sketch.util.TextUtils;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * SampleLogProxy
 */
public class SampleLogProxy implements SLog.Proxy {
    private OutLog2SDCard mOutLog2SDCard;

    public SampleLogProxy(Context context) {
        mOutLog2SDCard = new OutLog2SDCard(context);
    }

    @Override
    public int debug(String format, Object... objects) {
        mOutLog2SDCard.out("D", SLog.TAG, String.format(format, objects), null);
        return HiLog.debug(createLogLabel(), format, objects);
    }

    @Override
    public int info(String format, Object... objects) {
        mOutLog2SDCard.out("I", SLog.TAG, String.format(format, objects), null);
        return HiLog.info(createLogLabel(), format, objects);
    }

    @Override
    public int warn(String format, Object... objects) {
        mOutLog2SDCard.out("W", SLog.TAG, String.format(format, objects), null);
        return HiLog.warn(createLogLabel(), format, objects);
    }

    @Override
    public int error(String format, Object... objects) {
        mOutLog2SDCard.out("E", SLog.TAG, String.format(format, objects), null);
        return HiLog.error(createLogLabel(), format, objects);
    }

    private static HiLogLabel sLogLabel;
    private static final int DOMAIN = 0x001;

    private static HiLogLabel createLogLabel() {
        if (sLogLabel == null) {
            sLogLabel = new HiLogLabel(HiLog.LOG_APP, DOMAIN, SLog.TAG);
        }
        return sLogLabel;
    }

    @Override
    public void onReplaced() {
        mOutLog2SDCard.close();
    }

    private static class OutLog2SDCard {
        private Context mAppContext;
        private ObjectPool<LogEntry> logEntryObjectPool = null;
        private SimpleDateFormat logTimeDateFormat;
        private SimpleDateFormat fileNameDateFormat = null;
        private String logFileName = null;
        private FileWriter fileWriter = null;
        private Thread mWriteLogThread;
        private boolean closed = false;
        private String huanHang="";

        public OutLog2SDCard(Context context) {
            this.mAppContext = context.getApplicationContext();
            logEntryObjectPool = new ObjectPool<LogEntry>(new ObjectPool.ObjectFactory<LogEntry>() {
                @Override
                public @NotNull LogEntry newObject() {
                    return new LogEntry(logEntryObjectPool);
                }
            });
            logTimeDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.US);
            huanHang = context.getString(ResourceTable.String_huanhang);
        }

        /**
         * out
         * @param level
         * @param tag
         * @param msg
         * @param tr
         */
        public synchronized void out(String level, String tag, String msg, Throwable tr) {
            if (closed) {
                return;
            }

            String time = logTimeDateFormat.format(new Date());

            LogEntry logEntry = logEntryObjectPool.get();
            logEntry.set(time, level, tag, msg, tr);

            synchronized (this) {
                mWriteLogThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        writeLog(logEntry);
                    }
                }, "OutLogThread");
            }
            mWriteLogThread.start();
        }

        /**
         * close
         */
        public void close() {
            closed = true;

            if (mWriteLogThread != null) {
                mWriteLogThread.interrupt();
            }

            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                fileWriter = null;
            }

            if (logEntryObjectPool != null) {
                logEntryObjectPool.clear();
                logEntryObjectPool = null;
            }
        }

        private File makeLogFile(String newLogFileName) {
            File dir = mAppContext.getExternalCacheDir() != null ? mAppContext.getExternalCacheDir() : null;
            return new File(dir, "sketch_log" + File.separator + newLogFileName);
        }

        private void writeLog(LogEntry entry) {
            if (closed) {
                return;
            }

            if (fileNameDateFormat == null) {
                fileNameDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
            }

            Date date = new Date();
            String newLogFileName = fileNameDateFormat.format(date) + ".log";

            if (!newLogFileName.equals(logFileName) || fileWriter == null) {
                if (fileWriter != null) {
                    SketchUtils.close(fileWriter);
                }

                File logFile = makeLogFile(newLogFileName);

                if (!logFile.exists()) {
                    try {
                        boolean mkdirs = logFile.getParentFile().mkdirs();
                        boolean newFile = logFile.createNewFile();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }

                    if (!logFile.exists()) {
                        new IllegalStateException("Create file failed. " + logFile.getPath()).printStackTrace();
                        return;
                    }
                }

                try {
                    fileWriter = new FileWriter(logFile, true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                    return;
                }
                logFileName = newLogFileName;
            }
            fileWriteAction(entry);
        }

        private void fileWriteAction(LogEntry entry){
            try {
                fileWriter.write(entry.time);
                fileWriter.write(" ");
                fileWriter.write(entry.level);
                fileWriter.write(" ");
                fileWriter.write(entry.tag);
                if (!TextUtils.isEmpty(entry.message)) {
                    fileWriter.write(" ");
                    fileWriter.write(entry.message);
                }
                if (entry.tr != null) {
                    fileWriter.write(huanHang);
                    entry.tr.printStackTrace(new PrintWriter(fileWriter));
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            try {
                fileWriter.write(huanHang);
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            if (closed) {
                if (fileWriter != null) {
                    try {
                        fileWriter.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }

                    fileWriter = null;
                }
            } else {
                entry.recycle();
            }
        }
    }

    private static class LogEntry {
        private final ObjectPool<LogEntry> logEntryObjectPool;

        public LogEntry(ObjectPool<LogEntry> logEntryObjectPool) {
            this.logEntryObjectPool = logEntryObjectPool;
        }

        private String time = null;
        private String level = null;
        private String tag = null;
        private String message = null;
        private Throwable tr = null;

        /**
         * set
         *
         * @param time
         * @param level
         * @param tag
         * @param message
         * @param tr
         */
        public void set(String time, String level, String tag, String message, Throwable tr) {
            this.time = time;
            this.level = level;
            this.tag = tag;
            this.message = message;
            this.tr = tr;
        }

        /**
         * recycle
         */
        public void recycle() {
            set(null, null, null, null, null);
            logEntryObjectPool.put(this);
        }
    }
}